//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-04     ZhangYihua   first version
//
// Description  : Additive White Gaussian Noise 
//################################################################################

module awgn_prbs #(
parameter           AWGN_DW                 = 8,
parameter           CH_DW                   = 8,
parameter           CH_NUM                  = 8,    // 2<=CH_NUM<=16
parameter           GAIN_DW                 = 8,    // total bit-width
parameter           GAIN_QW                 = 5,    // fractional bit-width
parameter           SRC_PRBS_N              = 23,
parameter           CH_PRBS_N               = 11
) ( 
input                                       rst_n,
input                                       clk,

output  reg                                 awgn_vld,
output  reg signed  [AWGN_DW-1:0]           awgn_dat,
input                                       awgn_rdy,

input               [GAIN_DW-1:0]           cfg_gain
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          SRC_PRBS_DW             = MAX_F(SRC_PRBS_N,  CH_NUM);
localparam          CH_PRBS_DW              = MAX_F(CH_PRBS_N,   CH_DW);
localparam          PRBS_DW                 = MAX_F(SRC_PRBS_DW, CH_PRBS_DW);
localparam          EXP_DW                  = $clog2(CH_NUM);
localparam          SUM_DW                  = CH_DW+EXP_DW;
localparam          P_DW                    = SUM_DW+GAIN_DW;

wire                                        awgn_cke;
wire                [PRBS_DW-1:0]           src_prbs_cur;
wire                [PRBS_DW-1:0]           src_prbs_nxt;
reg                 [SRC_PRBS_DW-1:0]       src_prbs_dat;
reg                 [CH_PRBS_DW-1:0]        ch_prbs_dat[CH_NUM-1:0];
wire                [CH_NUM*CH_DW-1:0]      ch_dat;
wire                                        sum_vld;
wire                [SUM_DW-1:0]            sum_dat;
wire                [P_DW-1:0]              p_dat;
wire                [AWGN_DW+GAIN_QW-1:0]   p_dat_s;

//################################################################################
// main
//################################################################################

assign awgn_cke = (~awgn_vld) | awgn_rdy;

assign src_prbs_cur[0+:SRC_PRBS_DW] = src_prbs_dat;
assign src_prbs_nxt = prbs_nxt_f(src_prbs_cur, SRC_PRBS_N, CH_NUM, 1'b0, 1'b1);
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        src_prbs_dat <=`U_DLY {SRC_PRBS_DW{1'b0}};
    end else if (awgn_cke==1'b1) begin
        src_prbs_dat <=`U_DLY src_prbs_nxt[0+:SRC_PRBS_DW];
    end else
        ;
end

genvar g;
generate for (g=0; g<CH_NUM; g=g+1) begin:G_CH
    wire                [PRBS_DW-1:0]           ch_prbs_cur;
    wire                [PRBS_DW-1:0]           ch_prbs_nxt;

    assign ch_prbs_cur[0+:CH_PRBS_DW] = ch_prbs_dat[g] ^ {{CH_PRBS_DW-1{1'b0}}, src_prbs_dat[g]};
    assign ch_prbs_nxt = prbs_nxt_f(ch_prbs_cur, CH_PRBS_N, CH_DW, 1'b0, 1'b1);
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            ch_prbs_dat[g] <=`U_DLY {CH_PRBS_DW{1'b0}};
        end else if (awgn_cke==1'b1) begin
            ch_prbs_dat[g] <=`U_DLY ch_prbs_nxt[0+:CH_PRBS_DW];
        end else
            ;
    end
  
    assign ch_dat[g*CH_DW+:CH_DW] = ch_prbs_dat[g][0+:CH_DW];
end endgenerate

add_all #(
        .OPRD_BW                        (CH_DW                          ),	// bit width of single operand
        .OPRD_NUM                       (CH_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
        .OPRD_SIGNED                    (1'b1                           ),
        .LVL_REG                        ({EXP_DW{1'b1}}                 )
) u_add_all ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (awgn_cke                       ),

        .vld_in                         (1'b1                           ),
        .oprds                          (ch_dat                         ),

        .vld_out                        (sum_vld                        ),
        .sum_o                          (sum_dat                        )
);

assign p_dat = $signed(sum_dat)*$signed({1'b0, cfg_gain});

saturate #(.IW(P_DW), .OW(AWGN_DW+GAIN_QW)) u_sum_dat (.id(p_dat), .od(p_dat_s));

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        awgn_vld <=`U_DLY 1'b0;
        awgn_dat <=`U_DLY {AWGN_DW{1'b0}};
    end else if (awgn_cke==1'b1) begin
        awgn_dat <=`U_DLY p_dat_s[GAIN_QW+:AWGN_DW];
        awgn_vld <=`U_DLY sum_vld;
    end else
        ;
end

`include "func_prbs.v"  // refer to prbs_nxt_f()
`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
